/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import fs from 'fs';
import path from 'path';
import process from 'process';

import fse from 'fs-extra';

import { LocalFileWriter } from '../../../common/util/local-file-writer.js';
import { PathUtil } from '../../../common/util/path-util.js';
import { coreParameter } from '../../internal/data/global-core-parameters.js';
import { ReportListener } from './report-listener.js';
import { ReportService } from './report-service.js';

/**
 * 报告服务实现类
 *
 * @since 2022/8/18
 */
export class ReportServiceImpl implements ReportService {
  private reportListeners: ReportListener[] = [];
  private static instance: ReportServiceImpl;
  private static MAX_REPEAT_TIMES = 10;
  private static REPORT_REG = /^report(-monitor)?-[0-9]+\.json$/;
  private static HTML_REG = /^report-?[0-9]*.html$/;
  private static HTML_RESOURCE_NAME = 'htmlResource';
  private static UPLOAD_NAME = 'upload';
  private monitorTimeId: NodeJS.Timer | undefined;
  private static readonly VERSION = '1.0';
  private static buildId: string | undefined;
  private static readonly MB_CONVERTER = 1024;
  private static readonly REPORT_INTERVAL_MS = 1000;
  private static data: Array<{
    time: number;
    rss: number;
    heapTotal: number;
    heapUsed: number;
    external: number;
    arrayBuffers: number;
  }> = [];

  private constructor() {
    // just to specify the private constructor, use singleton pattern
  }


  report(): void {
    const reportObj: { [name: string]: any } = this.getReport();
    const reportDirPath = PathUtil.getReportDirPath();
    if (!fs.existsSync(reportDirPath)) {
      fs.mkdirSync(reportDirPath, { recursive: true });
    }
    this.deleteUnusableFiles(reportDirPath);
    this.storage(reportObj, reportDirPath);
  }

  getReport(): any {
    const reportObj: { [name: string]: any } = { version: '2.0', ppid: process.ppid };
    for (const listener of this.reportListeners) {
      const report = listener.queryReport();
      reportObj[report.getName()] = report.getValue();
    }
    return reportObj;
  }

  /**
   * 存储到report.json
   *
   * @param reportObj
   * @param reportDirPath
   */
  storage(reportObj: { [name: string]: any }, reportDirPath: string) {
    const files = fs.readdirSync(reportDirPath);
    const reports = files
      .filter((file) => file.startsWith('report-') && file.endsWith('json'))
      .sort((val1, val2) => {
        const file1 = path.resolve(reportDirPath, val1);
        const file2 = path.resolve(reportDirPath, val2);
        const stat1 = fs.statSync(file1);
        const stat2 = fs.statSync(file2);
        return stat2.birthtimeMs - stat1.birthtimeMs;
      });
    for (let i = 0; i < reports.length; i++) {
      if (i >= 9) {
        const report = path.resolve(reportDirPath, reports[i]);
        if (fs.existsSync(report)) {
          fs.unlinkSync(report);
        }
      }
    }
    // 由于存在循环import 这里使用动态加载，即在真正使用buildId时 再去import globalData
    const module = require('../../internal/data/global-data.js');
    if (module.globalData.buildId === undefined) {
      return;
    }
    const buildId = module.globalData.buildId;
    ReportServiceImpl.buildId = buildId;

    const reportFilePath = path.resolve(reportDirPath, `report-${buildId}.json`);
    LocalFileWriter.getInstance().write(reportFilePath, reportObj);
    if (enableHtmlGenerate()) {
      this.generateHtmlResource(reportDirPath, `report-${buildId}`, reportObj);
    }
  }

  /**
   * 删除除了report以外的无效文件
   */
  deleteUnusableFiles(reportDirPath: string) {
    const files = fs.readdirSync(reportDirPath);
    files.forEach((item) => {
      if (
        !ReportServiceImpl.REPORT_REG.test(item) &&
        (!ReportServiceImpl.HTML_REG.test(item) || enableHtmlGenerate()) &&
        item !== ReportServiceImpl.HTML_RESOURCE_NAME &&
        item !== ReportServiceImpl.UPLOAD_NAME
      ) {
        const unusableFile = path.resolve(reportDirPath, item);
        if (fs.existsSync(unusableFile)) {
          fs.unlinkSync(unusableFile);
        }
      }
    });
  }

  /**
   * 添加监听
   *
   * @param listener
   */
  addListener(listener: ReportListener): void {
    this.reportListeners.push(listener);
  }

  /**
   * 移除监听
   *
   * @param listener
   */
  removeListener(listener: ReportListener): void {
    const index = this.reportListeners.indexOf(listener);
    if (index !== -1) {
      this.reportListeners.splice(index, 1);
    }
  }

  generateHtmlResource(reportDirPath: string, reportFileName: string, reportObj: { [name: string]: any }): void {
    const htmlResourceDirPath = path.resolve(reportDirPath, 'htmlResource');
    const localHtmlResourcePath = path.resolve(__filename, '../../../../../res/staticHtmlResource/htmlResource');

    // 将html的js资源复制到报告处
    if (!fs.existsSync(htmlResourceDirPath)) {
      fse.copySync(localHtmlResourcePath, htmlResourceDirPath);
    } else {
      // 校验静态资源完整性
      const localHtmlResourceFiles = fs.readdirSync(localHtmlResourcePath);
      const targetHtmlResourceFiles = fs.readdirSync(htmlResourceDirPath);
      if (
        !localHtmlResourceFiles.every((file) => {
          if (!targetHtmlResourceFiles.includes(file)) {
            return false;
          }
          const localFileSize = fs.statSync(path.resolve(localHtmlResourcePath, file)).size;
          const targetFileSize = fs.statSync(path.resolve(htmlResourceDirPath, file)).size;
          return localFileSize === targetFileSize;
        })
      ) {
        fse.copySync(localHtmlResourcePath, htmlResourceDirPath);
      }
    }

    // 将report的数据注入到html文件的window上
    const initialHtmlPath = path.resolve(__filename, '../../../../../res/staticHtmlResource/index.html');
    const initialHtmlContent = fs.readFileSync(initialHtmlPath, 'utf8');
    const injectCode = `<script>window.__HVIGOR_REPORT__ = ${JSON.stringify(JSON.stringify(reportObj))};</script>`;
    const bodyIndex = initialHtmlContent.indexOf('</body>');
    const finalHtmlContent = initialHtmlContent.slice(0, bodyIndex) + injectCode + initialHtmlContent.slice(bodyIndex);
    const targetHtmlPath = path.resolve(reportDirPath, `${reportFileName}.html`);
    fs.writeFileSync(targetHtmlPath, finalHtmlContent);
  }

  static getInstance() {
    if (!ReportServiceImpl.instance) {
      ReportServiceImpl.instance = new ReportServiceImpl();
    }
    return ReportServiceImpl.instance;
  }

  startProcessMonitor(): void {
    if (this.monitorTimeId) {
      clearInterval(this.monitorTimeId);
    }

    ReportServiceImpl.data = [];

    this.monitorTimeId = setInterval(() => {
      const memory = process.memoryUsage();

      const memoryData = {
        time: Date.now(),
        rss: this.convertToMb(memory.rss),
        heapTotal: this.convertToMb(memory.heapTotal),
        heapUsed: this.convertToMb(memory.heapUsed),
        external: this.convertToMb(memory.external),
        arrayBuffers: this.convertToMb(memory.arrayBuffers),
      };

      ReportServiceImpl.data.push(memoryData);
    }, ReportServiceImpl.REPORT_INTERVAL_MS);
  }

  stopProcessMonitor(): void {
    if (this.monitorTimeId) {
      clearInterval(this.monitorTimeId);
      this.monitorTimeId = undefined;

      const jsonOutput = {
        version: ReportServiceImpl.VERSION,
        pid: process.pid,
        data: ReportServiceImpl.data,
      };

      if (ReportServiceImpl.buildId === undefined) {
        return;
      }

      const reportDirPath = PathUtil.getReportDirPath();
      const filePath = path.join(reportDirPath, `report-monitor-${ReportServiceImpl.buildId}.json`);
      fs.writeFileSync(filePath, JSON.stringify(jsonOutput, null, 2), 'utf-8');
    }
  }

  convertToMb(input: number): number {
    return input / ReportServiceImpl.MB_CONVERTER / ReportServiceImpl.MB_CONVERTER;
  }
}

function enableHtmlGenerate(): boolean {
  return typeof coreParameter.properties['hvigor.analyzeHtml'] === 'boolean' && coreParameter.properties['hvigor.analyzeHtml'];
}


